/**
File: csuSubspaceProject.c
Authors: J. Ross Beveridge, David Bolme, Kai She
Date: May 24, 2002
*/

/*
Copyright (c) 2003 Colorado State University

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

#define OPENING  "Project Test Images int Subspace and Compute Pairwise Distances."

/*
 Purpose: This program projects face images into a subspace and then
 computes the distances between faces with one of several alternative
 distance measures. The subspace is defined in a training file produced
 by csuSubspaceTrain.  The subspace may be either a simple PCA
 subspace, or it may be a PCA followed by LDA subspace. The images are
 specified in a file of image names. These may be grouped either as one
 name per line or several, although for this program there is no
 difference between the two in terms of what is done.  The resulting
 distances are written out to a series of files, one per image, all
 placed in a user specified directory.
*/
#include <sys/stat.h>
#include <stdio.h>

/*  CSU INCLUDES */
#include <csuCommon.h>

/*
 The command line arguments are managed by a single structure.
*/
typedef struct distDirNode {
    struct distDirNode *next;
    char* distDirectory;
    char* distName;
}
DistDirNode;

typedef struct {
    char* trainingFile;
    char* imageNamesFile;
    char* imageDirectory;
    DistDirNode* distList;
    int argc;
    char **argv;
}
Arguments;

/* ----------------------------------------------------------------------------------- */
/* Remind the user the usage of running this program.
   The command of running the program should be: run and the index of the training sets.
   All the other sets except the training sets will go to testing sets automatically.
   INPUT:  prog is the excutable program name.
*/
void usage(const char* prog) {
    printf("Usage: %s [OPTIONS] trianingFile imageNamesFile.[list/srt] [distanceDirectory distName]+\n", prog);
    printf("  Parameters:\n");
    printf("     trainingFile ....  Name of file containing subspace generated by training.\n");
    printf("     imageNamesFile...  Name of file containing test images: union of possible probe and gallery sets.\n");
    printf("  List of distance measures and corresponding distance directories: \n");
    printf("     distDirectory ...  Directory where distances files are to be written\n");
    printf("     distName ........  The distance measure to use, must be one of:\n");
    printf("        Euclidean ....  Standard Euclidean Distance, i.e. L2 norm.\n");
    printf("        Cityblock ....  Standard L1 norm.\n");
    printf("        Covariance ...  Sometimes called angle in FERET: dot product after unit normalization.\n");
    printf("        Correlation ..  Normalize images to mean zero and standard deviation one, then take dot product.\n");
    printf("        MahCosine ....  This measure is the cosine of the angle between the two feature vectors in Mahlinobis space.\n");
    printf("        MahL1 ........  L1 norm distance in Mahalanobis space.\n");
    printf("        MahL2 ........  L2 norm distance in Mahalanobis space.\n");
    printf("        YamborAngle ..  This is the same measure as \"MahAngle\" in \n"
           "                        version 4.0 and before.  It has been depricated \n"
	   "                        because the measure is not properly formulated.  \n"
	   "                        MahCosine has replaced it in newer versions. \n");
    printf("        YamborDist ...  This is the distance measure presented as \n"
           "                        \"Mahalinobis Distance\" in Wendy Yambor's \n"
	   "                        Thesis.  It is included here for completness.\n");
    printf("        ldaSoft ......  For LDA only, a variant of L2 use by Wenyi Zhao that weights dimensions by lambda.\n");
    printf("    -imDir <dir>:       image directory. DEFAULT = \".\"\n");
    printf("    -debuglevel <int>:  Level of debug information to display. DEFAULT = 0\n");
    printf("    -quiet:             Turn off all messages. DEFAULT = messages on\n");

    exit(1);
}

void process_command(int argc, char** argv, Arguments* args) {
    int i;
    int param_num = 0;

    /******* Set up default values *******/

    args->argc = argc;
    args->argv = argv;

    args->imageDirectory = strdup (".");

    args->distList = NULL;

    quiet = 0;
    debuglevel = 0;

    for (i = 1; i < argc; i++) {

      /* Catch common help requests */
      if      (readOption       (argc, argv, &i, "-help" )) { usage(argv[0]); }
      else if (readOption       (argc, argv, &i, "--help")) { usage(argv[0]); }

      /* Read in input directories */
      else if (readOptionString (argc, argv, &i, "-imDir", &(args->imageDirectory)));

      /* other flags */
      else if (readOption       (argc, argv, &i, "-quiet")) { quiet = 1; }
      else if (readOptionInt    (argc, argv, &i, "-debuglevel", &debuglevel));

      else if (param_num == 0) {
	args->trainingFile = strdup (argv[i]);
	param_num++;
      } else if (param_num == 1) {
	args->imageNamesFile = strdup (argv[i]);
	param_num++;
      } else if (param_num > 1) {
	DistDirNode* tmp = (DistDirNode*) malloc(sizeof(DistDirNode));
	tmp->next = args->distList;
	tmp->distDirectory = strdup (argv[i]);
	i++;
	tmp->distName = strdup (argv[i]);
	args->distList = tmp;
	param_num++;
      }
    }

    if (param_num < 3)
        usage(argv[0]);


  /* Print out the program parameters for appropriate debug level */

  DEBUG_INT (1, "Debuging enabled", debuglevel);

  if (debuglevel > 0)
    {
      printf ("***************** Program Parameters *********************\n");
      printf ("imageNamesFile: %s\n", args->imageNamesFile);
      printf ("imageDirectory: %s\n", args->imageDirectory);
    }
}


char** getNameByIndex(ImageList **srt, int numImages) {
    int i;
    ImageList *subject, *replicate;

    char** nameByIndex;

    /*  Allocate space for the image names for each index in images matrix */
    nameByIndex = (char**) malloc(sizeof(char*) * numImages);
    assert(nameByIndex);
    for (i = 0; i < numImages; i++) {
        nameByIndex[i] = (char*) malloc(sizeof(char) * FILE_LINE_LENGTH);
        assert(nameByIndex[i]);
    }

    /*  Move across columns and down rows of subject replicates table constructing
     an array of image file names indexed by the same index as the images matrix. */
    i = 0;
    for (subject = *srt; subject; subject = subject->next_subject) {
        for (replicate = subject; replicate; replicate = replicate->next_replicate) {
            if ((i != replicate->imageIndex) || (i == numImages)) {
                fprintf(stderr, "Error: replicate indices off or out of bounds.\n");
                exit(1);
            }
            strcpy(nameByIndex[i], replicate->filename);
            i++;
        }
    }
    return nameByIndex;
}


/* ===========================================================================
 MAIN

 The arguments are processed and then the subspace and related information is
 read from the training file written by csuSubspaceTrain.  The subspace basis
 is read into a matrix. If the basis vectors are for a PCA subspace, then the
 basis vectors are tested for orthonormality. While this should not be
 necessary, it is a prudent check to see that nothing has gone wrong either
 in the training phase or in the transcription of the subspace basis from the
 training code to the testing code.

 Once the training information is read, then the images specified in the
 imageNamesFile are read into the images matrix. This matrix is then mean
 centered using the mean, or centroid, associated with the training data.
 Next, the images are projected into subspace and the distances between all
 pairs of images are computed. Finally, these distances are written to files,
 one per image.
*/

int main(int argc, char *argv[]) {
    int i;
    Arguments args;
    int numImages;
    ImageList *srt;
    Matrix subspims, distances;
    Subspace subspace;
    char **nameByIndex;
    DistDirNode* ddn;

    MESSAGE(OPENING);
    MESSAGE(VERSION);
    process_command(argc, argv, &args);

    readSubspace (&subspace, args.trainingFile, 0);

    SAVE_MATRIX(subspace.values);
    
    MESSAGE1ARG("Reading image data from directory %s and projecting onto the new basis.", args.imageDirectory);
    subspims = readAndProjectImages(&subspace, args.imageNamesFile, args.imageDirectory, &numImages, &srt);

    
    for (ddn = args.distList; ddn != NULL; ddn = ddn->next) {
        MESSAGE1ARG("Computing distances with distance measure %s.", ddn->distName);
        distances = computeDistances(subspims, subspace.values, numImages, 0, ddn->distName);

        MESSAGE2ARG("Writing distance files for %d test images to directory %s", numImages, ddn->distDirectory);
        nameByIndex = getNameByIndex(&srt, numImages);
        for (i = 0; i < numImages; i++) {
            writeProgress("Writing distance files", i,numImages);
            writeDistancesForImage(ddn->distDirectory, nameByIndex[i], distances, i, nameByIndex);
        }
        freeMatrix(distances);
    }
    return 0;
}
